# monkey patching if patch #5390 isn't applied
from django.db import models
try:
    models.signals.m2m_add_items
except AttributeError:
    models.signals.m2m_add_items = object()
    models.signals.m2m_remove_items = object()
    models.signals.m2m_clear_items = object()
    import sys
    sys.stderr.write("WARNING! Django doesn't have signals for m2m relations. See #5390.")

try:
    from django.conf.settings import HISTORY_MARK
except ImportError:
    HISTORY_MARK = '_hst_mark'

from django.dispatch import dispatcher

from history.base import create_history_for_model
from history.helpers import chain_fields, get_user_ip
from history.signals import add_model_to_archive, add_m2m_to_archive, remove_m2m_from_archive, clear_m2m_from_archive

__all__ = ['History']


class History(object):
    def __init__(self, exclude_fields=()):
        self.exclude_fields = set(exclude_fields)

    def contribute_to_class(self, cls, name):
        # This should only get added once the class is otherwise complete
        def _contribute(sender):
            model, stored_fields = create_history_for_model(sender, self.exclude_fields)
            descriptor = HistoryDescriptor(model._default_manager)
            setattr(sender, name, descriptor)
            sender._stored_fields = stored_fields

            def _history_new(self):
                user, ip, comment = get_user_ip()
                chronicle = model(hst_original=self, hst_user=user, hst_ip=ip, hst_comment=comment)
                for f in chain_fields(self):
                    # excluded fields will be ignored by hasattr condition
                    if f.name in self._stored_fields:
                        setattr(chronicle, f.attname, f.value_from_object(self))

                chronicle.hst_revision = 1
                chronicle.save()
                setattr(self, HISTORY_MARK, True)
            sender._history_new = _history_new

            def _history(self, check_func, update_func):
                """
                Archive current state in history
                """
                try:
                    latest = getattr(self, name).latest()
                except model.DoesNotExist:
                    self._history_new()
                if check_func(latest):
                    if not hasattr(self, HISTORY_MARK):
                        user, ip, comment = get_user_ip()
                        latest = latest.clone_next_revision(hst_user=user, hst_ip=ip, hst_comment=comment)
                    update_func(latest)
                    latest.save()
                    setattr(self, HISTORY_MARK, True)
            sender._history = _history

            def _revert_to_revision(self, revision):
                archived = getattr(self, name).get(hst_revision=revision)
                for f in chain_fields(archived):
                    if f.name in self._stored_fields:
                        setattr(self, f.name, f.value_from_object(archived))
                self.save()
            sender._revert_to_revision = _revert_to_revision

            sender.last_editor = lambda self: getattr(self, name).latest().hst_user
            sender.last_edit = lambda self: getattr(self, name).latest().hst_saved

            # connect history methods
            dispatcher.connect(add_model_to_archive, signal=models.signals.post_save, sender=sender, weak=False)
            dispatcher.connect(add_m2m_to_archive, signal=models.signals.m2m_add_items, sender=sender, weak=False)
            dispatcher.connect(remove_m2m_from_archive, signal=models.signals.m2m_remove_items, sender=sender, weak=False)
            dispatcher.connect(clear_m2m_from_archive, signal=models.signals.m2m_clear_items, sender=sender, weak=False)

            # clean-up
            dispatcher.disconnect(_contribute, signal=models.signals.class_prepared, sender=cls, weak=False)

        dispatcher.connect(_contribute, signal=models.signals.class_prepared, sender=cls, weak=False)


class HistoryDescriptor(object):
    def __init__(self, manager):
        self.manager = manager

    def __get__(self, instance=None, owner=None):
        """Create a history manager based on the current object"""
        # this avoids scope conflict between HistoryDescriptor.self and RelatedHistoryManager.self
        self2 = self
        class RelatedHistoryManager(self2.manager.__class__):
            def __init__(self):
                self.model = self2.manager.model

            if instance is not None:
                def get_query_set(self):
                    return super(RelatedHistoryManager, self).get_query_set().filter(hst_original=instance._get_pk_val())
        return RelatedHistoryManager()

    def __set__(self, instance, value):
        raise AttributeError("History can't be edited in this manner.")
